Passed
Pull Request — master (#22)
by
unknown
02:29
created

WaveFileCreator.getiXML   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
1
/*
2
 * Copyright (c) 2017-2019 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a copy of this software and associated documentation files (the
6
 * "Software"), to deal in the Software without restriction, including
7
 * without limitation the rights to use, copy, modify, merge, publish,
8
 * distribute, sublicense, and/or sell copies of the Software, and to
9
 * permit persons to whom the Software is furnished to do so, subject to
10
 * the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
/**
26
 * @fileoverview The WaveFileCreator class.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
import { WaveFileParser } from "./wavefile-parser";
31
import { interleave, deInterleave } from "./parsers/interleave";
32
import { validateNumChannels } from "./validators/validate-num-channels";
33
import { validateSampleRate } from "./validators/validate-sample-rate";
34
import { packArrayTo, unpackArrayTo, packTo, unpack } from "./parsers/binary";
35
import { MpegReader } from "./mpeg-reader";
36
/**
37
 * A class to read, write and create wav files.
38
 * @extends WaveFileParser
39
 * @ignore
40
 */
41
export class WaveFileCreator extends WaveFileParser {
42
  constructor() {
43
    super();
44
    /**
45
     * The bit depth code according to the samples.
46
     * @type {string}
47
     */
48
    this.bitDepth = "0";
49
    /**
50
     * @type {!{bits: number, be: boolean}}
51
     * @protected
52
     */
53
    this.dataType = { bits: 0, be: false };
54
    /**
55
     * Audio formats.
56
     * Formats not listed here should be set to 65534,
57
     * the code for WAVE_FORMAT_EXTENSIBLE
58
     * @enum {number}
59
     * @protected
60
     */
61
    this.WAV_AUDIO_FORMATS = {
62
      "4": 17,
63
      "8": 1,
64
      "8a": 6,
65
      "8m": 7,
66
      "16": 1,
67
      "24": 1,
68
      "32": 1,
69
      "32f": 3,
70
      "64": 3,
71
      "65535": 80 // mpeg == 80
72
    };
73
  }
74
75
  /**
76
   * Set up the WaveFileCreator object based on the arguments passed.
77
   * Existing chunks are reset.
78
   * @param {number} numChannels The number of channels.
79
   * @param {number} sampleRate The sample rate.
80
   *    Integers like 8000, 44100, 48000, 96000, 192000.
81
   * @param {string} bitDepthCode The audio bit depth code.
82
   *    One of '4', '8', '8a', '8m', '16', '24', '32', '32f', '64'
83
   *    or any value between '8' and '32' (like '12').
84
   * @param {!(Array|TypedArray)} samples The samples.
85
   * @param {Object=} options Optional. Used to force the container
86
   *    as RIFX with {'container': 'RIFX'}
87
   * @throws {Error} If any argument does not meet the criteria.
88
   */
89
  fromScratch(numChannels, sampleRate, bitDepthCode, samples, options) {
90
    options = options || {};
91
    // reset all chunks
92
    this.clearHeaders();
93
    this.newWavFile_(numChannels, sampleRate, bitDepthCode, samples, options);
94
  }
95
96
  /**
97
   * Set up the WaveFileCreator object from an mpeg buffer and metadata info.
98
   * @param {!Uint8Array} mpegBuffer The buffer.
99
   * @param {Object=} info Mpeg info such as version, layer, bitRate, etc.
100
   * Required mpeg info:
101
   * info.version
102
   * info.layer
103
   * info.errorProtection
104
   * info.bitRate
105
   * info.sampleRate
106
   * info.padding
107
   * info.privateBit
108
   * info.channelMode
109
   * info.modeExtension
110
   * info.copyright
111
   * info.original
112
   * info.emphasis
113
   * info.numChannels
114
   * info.frameSize
115
   * info.sampleLength
116
   * @throws {Error} If any argument does not meet the criteria.
117
   */
118
  fromMpeg(mpegBuffer, info = null) {
119
    this.clearHeaders();
120
121
    if (info == null) {
0 ignored issues
show
Best Practice introduced by
Comparing info to null using the == operator is not safe. Consider using === instead.
Loading history...
122
      info = new MpegReader(mpegBuffer);
123
    }
124
125
    let codingHistory = this.mpegCodingHistory_(info);
126
127
    // riff(4) + fmt(40+8) + mext(12+8) + bxt(602+8+codingHistory.length) + fact(4+8) + buffer.length
128
    // 4 + 48 + 20 + 610 + 12 + codingHistory.length + buffer.length
129
    this.container = "RIFF";
130
    this.chunkSize = 694 + codingHistory.length + mpegBuffer.length;
131
    this.format = "WAVE";
132
    this.bitDepth = "65535";
133
134
    this.fmt.chunkId = "fmt ";
135
    this.fmt.chunkSize = 40;
136
    this.fmt.audioFormat = 80;
137
    this.fmt.numChannels = info.numChannels;
138
    this.fmt.sampleRate = info.sampleRate;
139
    this.fmt.byteRate = (info.bitRate / 8) * 1000;
140
    this.fmt.blockAlign = info.frameSize;
141
    this.fmt.bitsPerSample = 65535;
142
    this.fmt.cbSize = 22;
143
    this.fmt.headLayer = Math.pow(2, info.layer - 1);
144
    this.fmt.headBitRate = info.bitRate * 1000;
145
    this.fmt.headMode = this.mpegHeadMode_(info);
146
    this.fmt.headModeExt = this.mpegHeadModeExt_(info);
147
    this.fmt.headEmphasis = info.emphasis + 1;
148
    this.fmt.headFlags = this.mpegHeadFlags_(info);
149
    this.fmt.ptsLow = 0;
150
    this.fmt.ptsHigh = 0;
151
152
    this.mext.chunkId = "mext";
153
    this.mext.chunkSize = 12;
154
    this.mext.soundInformation = this.mpegSoundInformation_(info);
155
    this.mext.frameSize = info.frameSize;
156
    this.mext.ancillaryDataLength = 0;
157
    this.mext.ancillaryDataDef = 0;
158
    this.mext.reserved = "";
159
160
    this.bext.chunkId = "bext";
161
    this.bext.chunkSize = 602 + codingHistory.length;
162
    this.bext.timeReference = [0, 0];
163
    this.bext.version = 1;
164
    this.bext.codingHistory = codingHistory;
165
166
    this.fact.chunkId = "fact";
167
    this.fact.chunkSize = 4;
168
    this.fact.dwSampleLength = info.sampleLength;
169
170
    this.data.chunkId = "data";
171
    this.data.samples = mpegBuffer;
172
    this.data.chunkSize = this.data.samples.length;
173
  }
174
175
  mpegSoundInformation_(info) {
176
    let soundInformation = 0;
177
    if (info.homogeneous) {
178
      soundInformation += 1;
179
    }
180
    if (!info.padding) {
181
      soundInformation += 2;
182
    }
183
    if (
184
      (info.sampleRate == 44100 || info.sampleRate == 22050) &&
185
      info.padding
186
    ) {
187
      soundInformation += 4;
188
    }
189
    if (info.freeForm) {
190
      soundInformation += 8;
191
    }
192
    return soundInformation;
193
  }
194
195
  /**
196
   * Returns the mode value based on the channel mode of the mpeg file
197
   * @param {Object=} info Mpeg info such as version, layer, bitRate, etc.
198
   * @throws {Error} If any argument does not meet the criteria.
199
   */
200
  mpegHeadMode_(info) {
201
    return {
202
      stereo: 1,
203
      "joint-stereo": 2,
204
      "dual-mono": 4,
205
      mono: 8
206
    }[info.channelMode];
207
  }
208
209
  /**
210
   * Contains extra parameters for joint–stereo coding; not used for other modes.
211
   * @param {Object=} info Mpeg info such as version, layer, bitRate, etc.
212
   * @throws {Error} If any argument does not meet the criteria.
213
   */
214
  mpegHeadModeExt_(info) {
215
    if (info.channelMode == "joint-stereo") {
216
      return Math.pow(2, info.modeExtension);
217
    } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
218
      return 0;
219
    }
220
  }
221
222
  /**
223
   * Follows EBU established standards for CodingHistory for MPEG
224
   * EBU Technical Recommendation R98-1999, https://tech.ebu.ch/docs/r/r098.pdf
225
   * @param {Object=} info Mpeg info such as version, layer, bitRate, etc.
226
   * @throws {Error} If any argument does not meet the criteria.
227
   **/
228
  mpegCodingHistory_(info) {
229
    return (
230
      "A=MPEG" +
231
      info.version +
232
      "L" +
233
      info.layer +
234
      ",F=" +
235
      info.sampleRate +
236
      ",B=" +
237
      info.bitRate +
238
      ",M=" +
239
      info.channelMode +
240
      ",T=wavefile\r\n\0\0"
241
    );
242
  }
243
244
  /**
245
   * Follows EBU standards for `fmt` chunk `fwHeadFlags` for MPEG in BWF
246
   * EBU Tech. 3285–E – Supplement 1, 1997
247
   * https://tech.ebu.ch/docs/tech/tech3285s1.pdf
248
   * @param {Object=} info Mpeg info such as version, layer, bitRate, etc.
249
   * @throws {Error} If any argument does not meet the criteria.
250
   **/
251
  mpegHeadFlags_(info) {
252
    let flags = 0;
253
    if (info.privateBit) {
254
      flags += 1;
255
    }
256
    if (info.copyright) {
257
      flags += 2;
258
    }
259
    if (info.original) {
260
      flags += 4;
261
    }
262
    if (info.errorProtection) {
263
      flags += 8;
264
    }
265
    if (info.version > 0) {
266
      flags += 16;
267
    }
268
    return flags;
269
  }
270
271
  /**
272
   * Set up the WaveFileParser object from a byte buffer.
273
   * @param {!Uint8Array} wavBuffer The buffer.
274
   * @param {boolean=} [samples=true] True if the samples should be loaded.
275
   * @throws {Error} If container is not RIFF, RIFX or RF64.
276
   * @throws {Error} If format is not WAVE.
277
   * @throws {Error} If no 'fmt ' chunk is found.
278
   * @throws {Error} If no 'data' chunk is found.
279
   */
280
  fromBuffer(wavBuffer, samples = true) {
281
    super.fromBuffer(wavBuffer, samples);
282
    this.bitDepthFromFmt_();
283
    this.updateDataType_();
284
  }
285
286
  /**
287
   * Return a byte buffer representig the WaveFileParser object as a .wav file.
288
   * The return value of this method can be written straight to disk.
289
   * @return {!Uint8Array} A wav file.
290
   * @throws {Error} If bit depth is invalid.
291
   * @throws {Error} If the number of channels is invalid.
292
   * @throws {Error} If the sample rate is invalid.
293
   */
294
  toBuffer() {
295
    this.validateWavHeader_();
296
    return super.toBuffer();
297
  }
298
299
  /**
300
   * Return the samples packed in a Float64Array.
301
   * @param {boolean=} [interleaved=false] True to return interleaved samples,
302
   *   false to return the samples de-interleaved.
303
   * @param {Function=} [OutputObject=Float64Array] The sample container.
304
   * @return {!(Array|TypedArray)} the samples.
305
   */
306
  getSamples(interleaved = false, OutputObject = Float64Array) {
307
    /**
308
     * A Float64Array created with a size to match the
309
     * the length of the samples.
310
     * @type {!(Array|TypedArray)}
311
     */
312
    let samples = new OutputObject(
313
      this.data.samples.length / (this.dataType.bits / 8)
314
    );
315
    // Unpack all the samples
316
    unpackArrayTo(
317
      this.data.samples,
318
      this.dataType,
319
      samples,
320
      0,
321
      this.data.samples.length
322
    );
323
    if (!interleaved && this.fmt.numChannels > 1) {
324
      return deInterleave(samples, this.fmt.numChannels, OutputObject);
325
    }
326
    return samples;
327
  }
328
329
  /**
330
   * Return the sample at a given index.
331
   * @param {number} index The sample index.
332
   * @return {number} The sample.
333
   * @throws {Error} If the sample index is off range.
334
   */
335
  getSample(index) {
336
    index = index * (this.dataType.bits / 8);
337
    if (index + this.dataType.bits / 8 > this.data.samples.length) {
338
      throw new Error("Range error");
339
    }
340
    return unpack(
341
      this.data.samples.slice(index, index + this.dataType.bits / 8),
342
      this.dataType
343
    );
344
  }
345
346
  /**
347
   * Set the sample at a given index.
348
   * @param {number} index The sample index.
349
   * @param {number} sample The sample.
350
   * @throws {Error} If the sample index is off range.
351
   */
352
  setSample(index, sample) {
353
    index = index * (this.dataType.bits / 8);
354
    if (index + this.dataType.bits / 8 > this.data.samples.length) {
355
      throw new Error("Range error");
356
    }
357
    packTo(sample, this.dataType, this.data.samples, index, true);
358
  }
359
360
  /**
361
   * Return the value of the iXML chunk.
362
   * @return {string} The contents of the iXML chunk.
363
   */
364
  getiXML() {
365
    return this.iXML.value;
366
  }
367
368
  /**
369
   * Set the value of the iXML chunk.
370
   * @param {string} iXMLValue The value for the iXML chunk.
371
   * @throws {TypeError} If the value is not a string.
372
   */
373
  setiXML(iXMLValue) {
374
    if (typeof iXMLValue !== "string") {
375
      throw new TypeError("iXML value must be a string.");
376
    }
377
    this.iXML.value = iXMLValue;
378
    this.iXML.chunkId = "iXML";
379
  }
380
381
  /**
382
   * Get the value of the _PMX chunk.
383
   * @return {string} The contents of the _PMX chunk.
384
   */
385
  get_PMX() {
386
    return this._PMX.value;
387
  }
388
389
  /**
390
   * Set the value of the _PMX chunk.
391
   * @param {string} _PMXValue The value for the _PMX chunk.
392
   * @throws {TypeError} If the value is not a string.
393
   */
394
  set_PMX(_PMXValue) {
395
    if (typeof _PMXValue !== "string") {
396
      throw new TypeError("_PMX value must be a string.");
397
    }
398
    this._PMX.value = _PMXValue;
399
    this._PMX.chunkId = "_PMX";
400
  }
401
402
  /**
403
   * Set up the WaveFileCreator object based on the arguments passed.
404
   * @param {number} numChannels The number of channels.
405
   * @param {number} sampleRate The sample rate.
406
   *   Integers like 8000, 44100, 48000, 96000, 192000.
407
   * @param {string} bitDepthCode The audio bit depth code.
408
   *   One of '4', '8', '8a', '8m', '16', '24', '32', '32f', '64'
409
   *   or any value between '8' and '32' (like '12').
410
   * @param {!(Array|TypedArray)} samples The samples.
411
   * @param {Object} options Used to define the container.
412
   * @throws {Error} If any argument does not meet the criteria.
413
   * @private
414
   */
415
  newWavFile_(numChannels, sampleRate, bitDepthCode, samples, options) {
416
    if (!options.container) {
417
      options.container = "RIFF";
418
    }
419
    this.container = options.container;
420
    this.bitDepth = bitDepthCode;
421
    samples = interleave(samples);
422
    this.updateDataType_();
423
    /** @type {number} */
424
    let numBytes = this.dataType.bits / 8;
425
    this.data.samples = new Uint8Array(samples.length * numBytes);
426
    packArrayTo(samples, this.dataType, this.data.samples, 0, true);
427
    this.makeWavHeader_(
428
      bitDepthCode,
429
      numChannels,
430
      sampleRate,
431
      numBytes,
432
      this.data.samples.length,
433
      options
434
    );
435
    this.data.chunkId = "data";
436
    this.data.chunkSize = this.data.samples.length;
437
    this.validateWavHeader_();
438
  }
439
440
  /**
441
   * Define the header of a wav file.
442
   * @param {string} bitDepthCode The audio bit depth
443
   * @param {number} numChannels The number of channels
444
   * @param {number} sampleRate The sample rate.
445
   * @param {number} numBytes The number of bytes each sample use.
446
   * @param {number} samplesLength The length of the samples in bytes.
447
   * @param {!Object} options The extra options, like container defintion.
448
   * @private
449
   */
450
  makeWavHeader_(
451
    bitDepthCode,
452
    numChannels,
453
    sampleRate,
454
    numBytes,
455
    samplesLength,
456
    options
457
  ) {
458
    if (bitDepthCode == "4") {
459
      this.createADPCMHeader_(
460
        bitDepthCode,
461
        numChannels,
462
        sampleRate,
463
        numBytes,
464
        samplesLength,
465
        options
466
      );
467
    } else if (bitDepthCode == "8a" || bitDepthCode == "8m") {
468
      this.createALawMulawHeader_(
469
        bitDepthCode,
470
        numChannels,
471
        sampleRate,
472
        numBytes,
473
        samplesLength,
474
        options
475
      );
476
    } else if (
477
      Object.keys(this.WAV_AUDIO_FORMATS).indexOf(bitDepthCode) == -1 ||
478
      numChannels > 2
479
    ) {
480
      this.createExtensibleHeader_(
481
        bitDepthCode,
482
        numChannels,
483
        sampleRate,
484
        numBytes,
485
        samplesLength,
486
        options
487
      );
488
    } else {
489
      this.createPCMHeader_(
490
        bitDepthCode,
491
        numChannels,
492
        sampleRate,
493
        numBytes,
494
        samplesLength,
495
        options
496
      );
497
    }
498
  }
499
500
  /**
501
   * Create the header of a linear PCM wave file.
502
   * @param {string} bitDepthCode The audio bit depth
503
   * @param {number} numChannels The number of channels
504
   * @param {number} sampleRate The sample rate.
505
   * @param {number} numBytes The number of bytes each sample use.
506
   * @param {number} samplesLength The length of the samples in bytes.
507
   * @param {!Object} options The extra options, like container defintion.
508
   * @private
509
   */
510
  createPCMHeader_(
511
    bitDepthCode,
512
    numChannels,
513
    sampleRate,
514
    numBytes,
515
    samplesLength,
516
    options
517
  ) {
518
    this.container = options.container;
519
    this.chunkSize = 36 + samplesLength;
520
    this.format = "WAVE";
521
    this.bitDepth = bitDepthCode;
522
    this.fmt = {
523
      chunkId: "fmt ",
524
      chunkSize: 16,
525
      audioFormat: this.WAV_AUDIO_FORMATS[bitDepthCode] || 65534,
526
      numChannels: numChannels,
527
      sampleRate: sampleRate,
528
      byteRate: numChannels * numBytes * sampleRate,
529
      blockAlign: numChannels * numBytes,
530
      bitsPerSample: parseInt(bitDepthCode, 10),
531
      cbSize: 0,
532
      validBitsPerSample: 0,
533
      dwChannelMask: 0,
534
      subformat: []
535
    };
536
  }
537
538
  /**
539
   * Create the header of a ADPCM wave file.
540
   * @param {string} bitDepthCode The audio bit depth
541
   * @param {number} numChannels The number of channels
542
   * @param {number} sampleRate The sample rate.
543
   * @param {number} numBytes The number of bytes each sample use.
544
   * @param {number} samplesLength The length of the samples in bytes.
545
   * @param {!Object} options The extra options, like container defintion.
546
   * @private
547
   */
548
  createADPCMHeader_(
549
    bitDepthCode,
550
    numChannels,
551
    sampleRate,
552
    numBytes,
553
    samplesLength,
554
    options
555
  ) {
556
    this.createPCMHeader_(
557
      bitDepthCode,
558
      numChannels,
559
      sampleRate,
560
      numBytes,
561
      samplesLength,
562
      options
563
    );
564
    this.chunkSize = 40 + samplesLength;
565
    this.fmt.chunkSize = 20;
566
    this.fmt.byteRate = 4055;
567
    this.fmt.blockAlign = 256;
568
    this.fmt.bitsPerSample = 4;
569
    this.fmt.cbSize = 2;
570
    this.fmt.validBitsPerSample = 505;
571
    this.fact = {
572
      chunkId: "fact",
573
      chunkSize: 4,
574
      dwSampleLength: samplesLength * 2
575
    };
576
  }
577
578
  /**
579
   * Create the header of WAVE_FORMAT_EXTENSIBLE file.
580
   * @param {string} bitDepthCode The audio bit depth
581
   * @param {number} numChannels The number of channels
582
   * @param {number} sampleRate The sample rate.
583
   * @param {number} numBytes The number of bytes each sample use.
584
   * @param {number} samplesLength The length of the samples in bytes.
585
   * @param {!Object} options The extra options, like container defintion.
586
   * @private
587
   */
588
  createExtensibleHeader_(
589
    bitDepthCode,
590
    numChannels,
591
    sampleRate,
592
    numBytes,
593
    samplesLength,
594
    options
595
  ) {
596
    this.createPCMHeader_(
597
      bitDepthCode,
598
      numChannels,
599
      sampleRate,
600
      numBytes,
601
      samplesLength,
602
      options
603
    );
604
    this.chunkSize = 36 + 24 + samplesLength;
605
    this.fmt.chunkSize = 40;
606
    this.fmt.bitsPerSample = ((parseInt(bitDepthCode, 10) - 1) | 7) + 1;
607
    this.fmt.cbSize = 22;
608
    this.fmt.validBitsPerSample = parseInt(bitDepthCode, 10);
609
    this.fmt.dwChannelMask = dwChannelMask_(numChannels);
610
    // subformat 128-bit GUID as 4 32-bit values
611
    // only supports uncompressed integer PCM samples
612
    this.fmt.subformat = [1, 1048576, 2852126848, 1905997824];
613
  }
614
615
  /**
616
   * Create the header of mu-Law and A-Law wave files.
617
   * @param {string} bitDepthCode The audio bit depth
618
   * @param {number} numChannels The number of channels
619
   * @param {number} sampleRate The sample rate.
620
   * @param {number} numBytes The number of bytes each sample use.
621
   * @param {number} samplesLength The length of the samples in bytes.
622
   * @param {!Object} options The extra options, like container defintion.
623
   * @private
624
   */
625
  createALawMulawHeader_(
626
    bitDepthCode,
627
    numChannels,
628
    sampleRate,
629
    numBytes,
630
    samplesLength,
631
    options
632
  ) {
633
    this.createPCMHeader_(
634
      bitDepthCode,
635
      numChannels,
636
      sampleRate,
637
      numBytes,
638
      samplesLength,
639
      options
640
    );
641
    this.chunkSize = 40 + samplesLength;
642
    this.fmt.chunkSize = 20;
643
    this.fmt.cbSize = 2;
644
    this.fmt.validBitsPerSample = 8;
645
    this.fact = {
646
      chunkId: "fact",
647
      chunkSize: 4,
648
      dwSampleLength: samplesLength
649
    };
650
  }
651
652
  /**
653
   * Set the string code of the bit depth based on the 'fmt ' chunk.
654
   * @private
655
   */
656
  bitDepthFromFmt_() {
657
    if (this.fmt.audioFormat === 3 && this.fmt.bitsPerSample === 32) {
658
      this.bitDepth = "32f";
659
    } else if (this.fmt.audioFormat === 6) {
660
      this.bitDepth = "8a";
661
    } else if (this.fmt.audioFormat === 7) {
662
      this.bitDepth = "8m";
663
    } else if (this.fmt.audioFormat === 80) {
664
      this.bitDepth = "65535";
665
    } else {
666
      this.bitDepth = this.fmt.bitsPerSample.toString();
667
    }
668
  }
669
670
  /**
671
   * Validate the bit depth.
672
   * @return {boolean} True is the bit depth is valid.
673
   * @throws {Error} If bit depth is invalid.
674
   * @private
675
   */
676
  validateBitDepth_() {
677
    if (!this.WAV_AUDIO_FORMATS[this.bitDepth]) {
678
      if (parseInt(this.bitDepth, 10) > 8 && parseInt(this.bitDepth, 10) < 54) {
679
        return true;
680
      }
681
      throw new Error("Invalid bit depth.");
682
    }
683
    return true;
684
  }
685
686
  /**
687
   * Update the type definition used to read and write the samples.
688
   * @private
689
   */
690
  updateDataType_() {
691
    this.dataType = {
692
      bits: ((parseInt(this.bitDepth, 10) - 1) | 7) + 1,
693
      fp: this.bitDepth == "32f" || this.bitDepth == "64",
694
      signed: this.bitDepth != "8",
695
      be: this.container == "RIFX"
696
    };
697
    if (["4", "8a", "8m"].indexOf(this.bitDepth) > -1) {
698
      this.dataType.bits = 8;
699
      this.dataType.signed = false;
700
    }
701
  }
702
703
  /**
704
   * Validate the header of the file.
705
   * @throws {Error} If bit depth is invalid.
706
   * @throws {Error} If the number of channels is invalid.
707
   * @throws {Error} If the sample rate is invalid.
708
   * @ignore
709
   * @private
710
   */
711
  validateWavHeader_() {
712
    this.validateBitDepth_();
713
    if (!validateNumChannels(this.fmt.numChannels, this.fmt.bitsPerSample)) {
714
      throw new Error("Invalid number of channels.");
715
    }
716
    if (
717
      !validateSampleRate(
718
        this.fmt.numChannels,
719
        this.fmt.bitsPerSample,
720
        this.fmt.sampleRate
721
      )
722
    ) {
723
      throw new Error("Invalid sample rate.");
724
    }
725
  }
726
}
727
728
/**
729
 * Return the value for dwChannelMask according to the number of channels.
730
 * @param {number} numChannels the number of channels.
731
 * @return {number} the dwChannelMask value.
732
 * @private
733
 */
734
function dwChannelMask_(numChannels) {
735
  /** @type {number} */
736
  let mask = 0;
737
  // mono = FC
738
  if (numChannels === 1) {
739
    mask = 0x4;
740
    // stereo = FL, FR
741
  } else if (numChannels === 2) {
742
    mask = 0x3;
743
    // quad = FL, FR, BL, BR
744
  } else if (numChannels === 4) {
745
    mask = 0x33;
746
    // 5.1 = FL, FR, FC, LF, BL, BR
747
  } else if (numChannels === 6) {
748
    mask = 0x3f;
749
    // 7.1 = FL, FR, FC, LF, BL, BR, SL, SR
750
  } else if (numChannels === 8) {
751
    mask = 0x63f;
752
  }
753
  return mask;
754
}
755